package top.cardone.web.support;

import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.util.WebUtils;
import top.cardone.cache.Cache;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.core.CodeException;
import top.cardone.core.util.func.Func1;
import top.cardone.web.FuncBind;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * Func 绑定支持
 */
@Log4j2
public class FuncBindSupport {
    /**
     * api 配置根目录
     */
    @Setter
    private String apiConfigRoot;

    /**
     * api 静态根目录
     */
    @Setter
    private String apiStaticRoot;

    /**
     * 多个对象键名
     */
    @Setter
    private String objectsKey = "datas";

    /**
     * func 执行
     *
     * @param request
     * @return
     */
    public Object func(HttpServletRequest request) {
        return this.func(request, null);
    }

    public Object func(HttpServletRequest request, Object returnObject) {
        String beanId = request.getServletPath();

        FuncBind funcBind = (log.isDebugEnabled()) ? this.get(beanId) : this.getCache(beanId);

        if (funcBind.getFunc() == null) {
            return this.postHandle(request, beanId, returnObject, funcBind);
        }

        if (returnObject == null) {
            if (StringUtils.startsWith(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
                returnObject = this.funcByJson(request, funcBind.getFunc(), funcBind);
            } else {
                returnObject = this.funcByParameter(request, funcBind.getFunc(), funcBind);
            }
        }

        return this.postHandle(request, beanId, returnObject, funcBind);
    }

    /**
     * 输入参数为json
     *
     * @param request
     * @param func
     * @param funcBind
     * @return
     * @throws IOException
     */
    private Object funcByJson(HttpServletRequest request, Func1<Object, Object> func, FuncBind funcBind) {
        String jsonString;

        try (java.io.InputStream is = request.getInputStream()) {
            jsonString = IOUtils.toString(is, request.getCharacterEncoding());
        } catch (IOException e) {
            throw new CodeException("000012", new String[]{e.getMessage()}, e);
        }

        if (StringUtils.isBlank(jsonString)) {
            jsonString = "{}";
        }

        //输入参数为 map 时，优化获取 request 参数后，再合并
        if (Map.class.equals(funcBind.getParameterType())) {
            Map<String, Object> parameterMap = org.springframework.web.util.WebUtils.getParametersStartingWith(request, null);

            Map<String, Object> jsonMap = ApplicationContextHolder.getBean(Gson.class).fromJson(jsonString, Map.class);

            if (MapUtils.isNotEmpty(jsonMap)) {
                parameterMap.putAll(jsonMap);
            }

            return func.func(parameterMap);
        }

        if (java.lang.String.class.equals(funcBind.getParameterType()) || java.lang.Object.class.equals(funcBind.getParameterType())) {
            return func.func(jsonString);
        }

        return func.func(ApplicationContextHolder.getBean(Gson.class).fromJson(jsonString, funcBind.getParameterType()));
    }

    /**
     * 输入参数为请求键值对
     *
     * @param request
     * @param func
     * @param funcBind
     * @return
     */
    private Object funcByParameter(HttpServletRequest request, Func1<Object, Object> func, FuncBind funcBind) {
        Map<String, Object> parameterMap = org.springframework.web.util.WebUtils.getParametersStartingWith(request, null);

        if (java.util.Map.class.equals(funcBind.getParameterType()) || java.lang.Object.class.equals(funcBind.getParameterType())) {
            return func.func(parameterMap);
        }

        String jsonString = ApplicationContextHolder.getBean(Gson.class).toJson(parameterMap);

        if (java.lang.String.class.equals(funcBind.getParameterType()) || java.lang.Object.class.equals(funcBind.getParameterType())) {
            return func.func(jsonString);
        }

        return func.func(ApplicationContextHolder.getBean(Gson.class).fromJson(jsonString, funcBind.getParameterType()));
    }

    /**
     * 设置 func
     *
     * @param funcBind func 绑定对象
     * @param beanId
     * @return
     */
    private FuncBind get(FuncBind funcBind, String beanId) {
        Func1<Object, Object> func = ApplicationContextHolder.getBean(Func1.class, beanId);

        if (func == null) {
            return funcBind;
        }

        funcBind.setFunc(func);

        for (Method method : func.getClass().getDeclaredMethods()) {
            if (!"func".equals(method.getName())) {
                continue;
            }

            Class<?> parameterType = method.getParameterTypes()[0];

            if (!java.lang.Object.class.equals(parameterType)) {
                funcBind.setParameterType(parameterType);

                return funcBind;
            }
        }

        return funcBind;
    }

    /**
     * 获取 func 绑定
     *
     * @param beanId
     * @return
     */
    private FuncBind get(String beanId) {
        try {
            String apiConfigFilePath = this.apiConfigRoot + beanId;

            Resource resource = ApplicationContextHolder.getApplicationContext().getResource(apiConfigFilePath);

            if (!resource.exists()) {
                return this.get(new FuncBind(), beanId);
            }

            try (InputStream is = resource.getInputStream()) {
                String apiConfigJson = IOUtils.toString(is, Charsets.UTF_8);

                if (StringUtils.isBlank(apiConfigJson)) {
                    apiConfigJson = "{}";
                }

                FuncBind funcBind = ApplicationContextHolder.getBean(Gson.class).fromJson(apiConfigJson, FuncBind.class);

                return this.get(funcBind, beanId);
            }
        } catch (IOException e) {
            throw new CodeException("000013", new String[]{e.getMessage()}, e);
        }
    }

    /**
     * 获取 func 绑定缓存
     *
     * @param beanId
     * @return
     */
    private FuncBind getCache(String beanId) {
        return ApplicationContextHolder.getBean(Cache.class).get(FuncBindSupport.class.getName(), beanId, () -> this.get(beanId));
    }

    /**
     * 执行后
     *
     * @param request
     * @param servletPath
     * @param returnObject
     * @param funcBind
     * @return
     */
    private Object postHandle(HttpServletRequest request, String servletPath, Object returnObject, FuncBind funcBind) {
        if (MapUtils.isEmpty(funcBind.getPostHandle()) && (returnObject == null)) {
            return this.readApiStaticFileToString(request, servletPath);
        }

        Map<String, Object> returnMap;

        if (returnObject == null) {
            returnMap = Maps.newHashMap();
        } else if (returnObject instanceof Map) {
            returnMap = (Map<String, Object>) returnObject;
        } else if ((returnObject instanceof Iterable) || ObjectUtils.isArray(returnObject)) {
            returnMap = Maps.newHashMap();

            returnMap.put(this.objectsKey, returnObject);
        } else {
            returnMap = Maps.newHashMap();

            ReflectionUtils.doWithFields(returnObject.getClass(), field -> {
                Object value = FieldUtils.readField(field, returnObject, true);

                returnMap.put(field.getName(), value);
            }, ReflectionUtils.COPYABLE_FIELDS);
        }

        if (MapUtils.isEmpty(funcBind.getPostHandle())) {
            return returnMap;
        }

        for (Map.Entry<String, Object> postHandleEntry : funcBind.getPostHandle().entrySet()) {
            if (!(postHandleEntry.getValue() instanceof Map)) {
                continue;
            }

            Map<String, Object> postHandleEntryValue = (Map<String, Object>) postHandleEntry.getValue();

            String beanId = MapUtils.getString(postHandleEntryValue, "beanId");

            if (StringUtils.isEmpty(beanId)) {
                continue;
            }

            Object params = MapUtils.getObject(postHandleEntryValue, "params");

            returnMap.put(postHandleEntry.getKey(), ApplicationContextHolder.func(Func1.class, func -> func.func(params), beanId));
        }

        return returnMap;
    }

    /**
     * 读取api静态文件
     *
     * @param request
     * @param servletPath
     * @return
     */
    private String readApiStaticFileToString(HttpServletRequest request, String servletPath) {
        try {
            String apiStaticFilePath;

            if (StringUtils.isBlank(apiStaticRoot)) {
                apiStaticFilePath = WebUtils.getRealPath(request.getServletContext(), servletPath);
            } else {
                apiStaticFilePath = apiStaticRoot + servletPath;
            }

            Resource resource = ApplicationContextHolder.getApplicationContext().getResource(apiStaticFilePath);

            if (!resource.exists()) {
                return "{}";
            }

            try (InputStream is = resource.getInputStream()) {
                return IOUtils.toString(is, Charsets.UTF_8);
            }
        } catch (IOException e) {
            throw new CodeException("000011", new String[]{e.getMessage()}, e);
        }
    }
}